package com.nariano.server.domain.entities;

import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Transient;

import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.springframework.util.Assert;

@Entity
@Table(name="area")
public class Area implements Serializable {
    
	@Id
	@GeneratedValue
	@Column(name="area_id")
    private Integer id;
	
	@Embedded
	@AttributeOverrides( {
        @AttributeOverride(name="x", column = @Column(name="location_x") ),
        @AttributeOverride(name="y", column = @Column(name="location_y") )
	} )
    private Location location;
	
	@Column(name="resource_type_id")
	private String hibernateResourceType = ResourceType.FIELD.getId();
    
    @ManyToMany(fetch = FetchType.EAGER, cascade=CascadeType.ALL)
    @JoinTable(name="area_population",
    	joinColumns=
            @JoinColumn(name="area_id", referencedColumnName="area_id"),
        inverseJoinColumns=
            @JoinColumn(name="population_id", referencedColumnName="population_id")
    )
    private Set<Population> hibernatePopulation = new HashSet<Population>();

    @OneToOne(fetch = FetchType.EAGER, cascade=CascadeType.ALL)
    @JoinColumn(name="area_id")
    private City city;
    
    @Transient
    private Map<Integer, PopulationEntry> populationMap;
    
    public Area() {
    
    }

    public Area(Location location) {
        setLocation(location);
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Location getLocation() {
		return location;
	}

	public void setLocation(Location location) {
		this.location = location;
	}

    public ResourceType getResourceType() {
        return ResourceType.resolve(hibernateResourceType);
    }

    public void setResourceType(ResourceType resource) {
        this.hibernateResourceType = resource.getId();
    }
    
	private void initPopulationMap() {
		if (populationMap!=null) {
			return;
		}
		Map<Integer, PopulationEntry> populationMap = new HashMap<Integer, PopulationEntry>();
		for (Population p : hibernatePopulation) {
			if (false == id.equals(p.getPositionArea())) {
				p.setPositionArea(id);
			}
			if (p.getNumber()==0) {
				continue;
			}
			PopulationEntry entry = populationMap.get(p.getUserId());
			if (entry==null) {
				entry = new PopulationEntry(p.getUserId());
				populationMap.put(p.getUserId(), entry);
			}
			entry.addPopulation(p);
		}
		this.populationMap = populationMap;
	}

    @Override
    public String toString() {
        return ReflectionToStringBuilder.toString(this, ToStringStyle.SHORT_PREFIX_STYLE);
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((id == null) ? 0 : id.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Area other = (Area) obj;
        if (id == null) {
            if (other.id != null)
                return false;
        } else if (!id.equals(other.id))
            return false;
        return true;
    }
   
    public City getCity() {
        return city;
    }

    public void setCity(City city) {
        this.city=city;
    }
    
    public int getPopulationCount() {
        int count = 0;
        for (Population p : hibernatePopulation) {
            count += p.getNumber();
        }
        return count;
    }
    
    public Population findPopulation(PopulationType type) {
        for(Population p : hibernatePopulation) {
            if (p.type().equals(type)){
                return p;
            }
        }
        return null;
    }

	public Population moveOutRandomPopulation(Integer user) {
		initPopulationMap();
		PopulationEntry areaPopulationEntry = populationMap.get(user);
		if (areaPopulationEntry==null) {
			return null;
		}
		if (areaPopulationEntry.getCount()==0) {
			throw new IllegalStateException("Cannot be 0, should have been removed");
		}
		Population pop = areaPopulationEntry.moveOutRandomPopulation();
		if (areaPopulationEntry.getCount()==0) { // user is not present in this area anymore
			populationMap.remove(user);
		}
		updateHibernatePopulation();
		return pop;
	}


	public void addPopulation(Population p) {
		initPopulationMap();
		Assert.notNull(p);
		if (p.getNumber()==0) { 
			return; // don't add since it's 0
		}
		PopulationEntry areaPopulationEntry = populationMap.get(p.getUserId());
		if (areaPopulationEntry==null) {
			areaPopulationEntry = new PopulationEntry(p.getUserId());
			populationMap.put(p.getUserId(), areaPopulationEntry);
		}
		areaPopulationEntry.addPopulation(p);
		updateHibernatePopulation();
	}
	
	private void updateHibernatePopulation() {
		initPopulationMap();
		hibernatePopulation.clear();
		for (PopulationEntry entry : populationMap.values()) {
			hibernatePopulation.addAll(entry.getPopulations());
		}
	}

	public Collection<Integer> getUsers() {
		initPopulationMap();
		return populationMap.keySet();
	}
	
	public int getUsersCount() {
		initPopulationMap();
		return populationMap.size();
	}

	public Population peekRandomPopulation(Integer user) {
		initPopulationMap();
		PopulationEntry areaPopulationEntry = populationMap.get(user);
		if (areaPopulationEntry==null) {
			return null;
		}
		return areaPopulationEntry.peekRandomPopulation();
	}

	public Population moveOutAllPopulations(Integer user, PopulationType type) {
		Assert.isTrue(getUsersCount()==1 ,"Cannot flee a conflict");
		return this.populationMap.get(user).moveOutAllPopulations(type);
	}


}
